import axios, {
  AxiosError,
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  CreateAxiosDefaults
} from "axios";
import {
  RequestOption,
  ResponseType,
  ResponseData,
  MappedType,
  FlatResponseData
} from "./typings";
import { RequestErrorCode } from "./constant";
import { error } from "console";

function createDefaultOptions(options?: Partial<RequestOption>) {
  const opts: RequestOption = {
    onRequest: async config => config,
    isSuccess: () => true,
    onFail: async () => {},
    onError: async () => {},
    transformResponse: response => response.data
  };
  Object.assign(opts, options || {});
  return opts;
}

function createInstance(
  axiosConfig: CreateAxiosDefaults,
  options?: Partial<RequestOption>
): { instance: AxiosInstance; opts: RequestOption } {
  const opts = createDefaultOptions(options);

  const instance = axios.create(axiosConfig);

  instance.interceptors.request.use(
    async config => {
      const handledConfig = (await opts.onRequest?.(config)) || config;
      return handledConfig;
    },
    error => {
      return Promise.reject(error);
    }
  );

  instance.interceptors.response.use(
    async response => {
      const responseType = response.config?.responseType || "json";

      if (responseType !== "json" || opts.isSuccess(response)) {
        return Promise.resolve(response);
      }

      const fail = await opts.onFail(response, instance);
      if (fail) {
        return fail;
      }

      const Error = new AxiosError(
        "the request error",
        RequestErrorCode,
        response.config,
        response.request,
        response
      );

      await opts.onError(Error);

      return Promise.reject(Error);
    },
    async (error: AxiosError<ResponseData>) => {
      await opts.onError(error);

      return Promise.reject(error);
    }
  );

  return { instance, opts };
}

/**
 * 创建axios请求体
 * @param axiosConfig
 * @param options
 * @returns
 */
export function createRequest(
  axiosConfig: CreateAxiosDefaults,
  options?: Partial<RequestOption>
) {
  const { instance, opts } = createInstance(axiosConfig, options);

  const request = async <T = any, R extends ResponseType = "json">(
    config: AxiosRequestConfig
  ): Promise<FlatResponseData<T, R>> => {
    try {
      const response: AxiosResponse<ResponseData<T>> = await instance(config);
      const responseType = response.config?.responseType || "json";

      if (responseType === "json") {
        const data = opts.transformResponse(response) as unknown as T;

        return { data, error: null, response };
      }

      return {
        data: response.data as unknown as MappedType<R, T>,
        error: null,
        response
      };
    } catch (error) {
      return {
        data: null,
        error: error as AxiosError<ResponseData<T>>,
        response: (error as AxiosError<ResponseData<T>>).response
      };
    }
  };

  return request;
}
